package com.isyscore.boot.quartz;

import lombok.AllArgsConstructor;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.lang.NonNull;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.util.CollectionUtils;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;

/**
 * @author felixu
 * @since 2021.05.27
 */
@AllArgsConstructor
public class QuartzTemplate {

    private final Scheduler scheduler;

    /**
     * 增加一个任务
     * 该任务为每隔多少秒的间隔执行一次
     *
     * @param clazz    任务实现类
     * @param name     任务名称
     * @param group    任务组名
     * @param interval 时间间隔，单位毫秒
     * @param times    运行的次数，大于 0 生效，小于等于 0 为不限次数
     * @param params   参数
     * @param start    任务开始时间
     */
    public void addJob(@NonNull Class<? extends QuartzJobBean> clazz, @NonNull String name, @NonNull String group,
                       long interval, int times, Map<String, Object> params, LocalDateTime start) throws SchedulerException {
        // 构建任务
        JobDetail jobDetail = JobBuilder.newJob(clazz).withIdentity(name, group).build();
        // 添加参数
        if (!CollectionUtils.isEmpty(params))
            jobDetail.getJobDataMap().putAll(params);
        // 设置执行规则
        SimpleScheduleBuilder builder = SimpleScheduleBuilder.repeatSecondlyForever(1).withIntervalInMilliseconds(interval);
        if (times > 0)
            builder.withRepeatCount(times - 1);
        Trigger trigger = TriggerBuilder.newTrigger().withIdentity(name, group).withSchedule(builder)
                .startAt(startAt(start))
                .build();
        scheduler.scheduleJob(jobDetail, trigger);
    }

    /**
     * 增加一个任务
     * 该任务指定 cron 表达式执行
     *
     * @param clazz  任务实现类
     * @param name   任务名称
     * @param group  任务组名
     * @param cron   cron 表达式，如：0/5 * * * * ?
     * @param params 参数
     * @param start  任务开始时间
     */
    public void addJob(@NonNull Class<? extends QuartzJobBean> clazz, @NonNull String name, @NonNull String group,
                       @NonNull String cron, Map<String, Object> params, LocalDateTime start) throws SchedulerException {
        // 构建任务
        JobDetail jobDetail = JobBuilder.newJob(clazz).withIdentity(name, group).build();
        // 添加参数
        if (!CollectionUtils.isEmpty(params))
            jobDetail.getJobDataMap().putAll(params);
        // 设置执行规则
        // 源码修改
        //不触发立即执行
        //等待下次Cron触发频率到达时刻开始按照Cron频率依次执行
        Trigger trigger = TriggerBuilder.newTrigger().withIdentity(name, group).withSchedule(CronScheduleBuilder.cronSchedule(cron).withMisfireHandlingInstructionDoNothing())
                .startAt(startAt(start))
                .build();
        scheduler.scheduleJob(jobDetail, trigger);
    }

    /**
     * 删除一个任务
     *
     * @param group 任务组名
     * @param name  任务名称
     */
    public void deleteJob(@NonNull String group, @NonNull String name) throws SchedulerException {
        scheduler.deleteJob(JobKey.jobKey(name, group));
    }

    /**
     * 删除某一组的所有任务
     *
     * @param group 任务组名
     */
    public void deleteJobsByGroup(@NonNull String group) throws SchedulerException {
        scheduler.deleteJobs(new ArrayList<>(scheduler.getJobKeys(GroupMatcher.groupEquals(group))));
    }

    /**
     * 暂停一个任务
     *
     * @param name  任务名称
     * @param group 任务组名
     */
    public void pauseJob(@NonNull String name, @NonNull String group) throws SchedulerException {
        scheduler.pauseJob(JobKey.jobKey(name, group));
    }

    /**
     * 暂停某组任务
     *
     * @param group 任务组名
     */
    public void pauseJobsByGroup(@NonNull String group) throws SchedulerException {
        scheduler.pauseJobs(GroupMatcher.groupEquals(group));
    }

    /**
     * 恢复一个任务
     *
     * @param name  任务名称
     * @param group 任务组名
     */
    public void resumeJob(@NonNull String name, @NonNull String group) throws SchedulerException {
        scheduler.resumeJob(JobKey.jobKey(name, group));
    }

    /**
     * 恢复某组任务
     *
     * @param group 任务组名
     */
    public void resumeJob(@NonNull String group) throws SchedulerException {
        scheduler.resumeJobs(GroupMatcher.groupEquals(group));
    }

    /**
     * 立即执行一个任务
     *
     * @param name  任务名称
     * @param group 任务组名
     */
    public void run(@NonNull String name, @NonNull String group) throws SchedulerException {
        scheduler.triggerJob(JobKey.jobKey(name, group));
    }

    /**
     * 获取所有任务列表
     *
     * @return 所有任务
     */
    public List<QuartzJobInfo> schedulers() throws SchedulerException {
        Set<JobKey> keys = scheduler.getJobKeys(GroupMatcher.anyJobGroup());
        if (CollectionUtils.isEmpty(keys))
            return Collections.emptyList();
        List<QuartzJobInfo> result = new ArrayList<>();
        for (JobKey key : keys) {
            List<? extends Trigger> triggers = scheduler.getTriggersOfJob(key);
            for (Trigger trigger : triggers) {
                result.add(toQuartzJobInfo(key, trigger));
            }
        }
        return result;
    }

    /**
     * 获取所有正在运行的任务
     *
     * @return 正在运行的任务
     */
    public List<QuartzJobInfo> currentlyExecutingJobs() throws SchedulerException {
        List<JobExecutionContext> jobs = scheduler.getCurrentlyExecutingJobs();
        if (CollectionUtils.isEmpty(jobs))
            return Collections.emptyList();
        List<QuartzJobInfo> result = new ArrayList<>(jobs.size());
        for (JobExecutionContext executingJob : jobs) {
            result.add(toQuartzJobInfo(executingJob.getJobDetail().getKey(), executingJob.getTrigger()));
        }
        return result;
    }

    private Date startAt(LocalDateTime start) {
        return Objects.isNull(start) ? new Date() : Date.from(start.atZone(ZoneId.systemDefault()).toInstant());
    }

    private QuartzJobInfo toQuartzJobInfo(JobKey key, Trigger trigger) throws SchedulerException {
        QuartzJobInfo job = new QuartzJobInfo();
        job.setName(key.getName());
        job.setGroup(key.getGroup());
        job.setState(scheduler.getTriggerState(trigger.getKey()));
        job.setStart(LocalDateTime.ofInstant(trigger.getStartTime().toInstant(), ZoneId.systemDefault()));
        if (trigger instanceof CronTrigger) {
            job.setType(QuartzJobInfo.JobType.CRON);
            job.setCron(((CronTrigger) trigger).getCronExpression());

        } else {
            job.setType(QuartzJobInfo.JobType.INTERVAL);
            job.setInterval(((SimpleTrigger) trigger).getRepeatInterval());
        }
        return job;
    }
}
